﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UnitOfWork.cs" company="SaltFx">
//   Copyright (c) SaltFx - All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace SaltFx.Data.RavenDB
{
	using System;
	using System.Transactions;

	using Raven.Client;

	/// <summary>
	/// The RavenDB unit of work.
	/// </summary>
	/// <example>
	/// Sample usage:
	///   <code>
	/// using(unitOfWork.Start())
	/// {
	///     // Do stuff...
	///     //
	///     unitOfWork.Commit();
	/// }
	///   </code>
	/// </example>
	public sealed class UnitOfWork : IUnitOfWork
	{
		#region Constants and Fields

		/// <summary>
		/// The active data session manager.
		/// </summary>
		private IActiveDataSessionManager<IDocumentSession> activeDataSessionManager;

		/// <summary>
		/// The data session provider.
		/// </summary>
		private IDataSessionProvider<IDocumentSession> dataSessionProvider;

		#endregion

		/// <summary>
		/// Initializes a new instance of the <see cref="UnitOfWork" /> class.
		/// </summary>
		/// <param name="dataSessionProvider">The data session provider.</param>
		/// <param name="activeDataSessionManager">The active data session manager.</param>
		public UnitOfWork(
			IDataSessionProvider<IDocumentSession> dataSessionProvider,
			IActiveDataSessionManager<IDocumentSession> activeDataSessionManager)
		{
			this.dataSessionProvider = dataSessionProvider;
			this.activeDataSessionManager = activeDataSessionManager;
		}

		#region Public Properties

		/// <summary>
		/// Gets a value indicating whether the unit of work is started.
		/// </summary>
		public bool IsStarted
		{
			get
			{
				return this.CurrentSession != null;
			}
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets the current session.
		/// </summary>
		private IDocumentSession CurrentSession
		{
			get
			{
				return this.activeDataSessionManager.Current;
			}
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Flushes the unit of work and commits the transaction.
		/// </summary>
		public void Commit()
		{
			Guard.Will.ThrowException(Strings.NoRavenDBDocumentSession).When(this.CurrentSession == null);

			using (var transaction = new TransactionScope())
			{
				try
				{
					this.CurrentSession.SaveChanges();
				}
				catch (Exception ex)
				{
					transaction.Dispose();

					throw new SaltFxDataException("Unable to commit the transaction. See inner exception for details.", ex);
				}

				transaction.Complete();
			}
		}

		/// <summary>
		/// Disposes of the unit of work.
		/// </summary>
		public void Dispose()
		{
			this.activeDataSessionManager.ClearActiveDataSession();
		}

		/// <summary>
		/// Rolls back the unit of work changes.
		/// </summary>
		public void Rollback()
		{
			this.Dispose();
		}

		/// <summary>
		/// Starts the unit of work.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This method creates and returns an instance of a unit of work. The IUnitOfWork interface implements
		/// IDisposable which means that a business transaction has ended when the UnitOfWork is disposed. It's
		/// important to remember that if we simply dispose of the unit of work then nothing will happen. If we want to
		/// propagate changes to the database, we have to call the Commit method on the unit of work.
		/// </para>
		/// </remarks>
		/// <returns>
		/// A unit of work.
		/// </returns>
		public IUnitOfWork Start()
		{
			if (this.IsStarted)
			{
				throw new SaltFxDataException(Strings.UnitOfWorkAlreadyStarted);
			}

			this.activeDataSessionManager.SetActiveDataSession(this.dataSessionProvider.Create());

			if (this.CurrentSession == null)
			{
				throw new SaltFxDataException(Strings.UnableToCreateRavenDBDocumentSession);
			}

			return this;
		}

		#endregion
	}
}